﻿/*This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>
	*/

using System;
using System.IO;
using System.IO.Compression;

namespace ZipLib
{
    internal class Crc32
    {
        private readonly UInt32[] _crc32Table;
        private const int BufferSize = 1024;

        /// <summary>
        /// Initialize the Crc32 table
        /// </summary>
        public Crc32()
        {
            // Official polynomial used in PKzip
            const UInt32 dwPolynomial = 0xEDB88320U;

            _crc32Table = new UInt32[256];

            for (uint i = 0; i < 256; i++)
            {
                UInt32 dwCrc = i;
                for (int j = 0; j < 8; j++)
                {
                    if ((dwCrc & 1) == 1)
                    {
                        dwCrc = (dwCrc >> 1) ^ dwPolynomial;
                    }
                    else
                    {
                        dwCrc >>= 1;
                    }
                }
                _crc32Table[i] = dwCrc;
            }
        }

        /// <summary>
        /// Check CRC32 hash value with given stream and compress the stream at the same time.
        /// </summary>
        /// <param name="input">Compressed stream</param>
        /// <param name="decompressed">Decompressed stream</param>
        /// <returns>Crc32 hash value</returns>
        protected internal uint CheckCrc32(Stream input, out Stream decompressed)
        {
            decompressed = new MemoryStream();
            using (DeflateStream ds = new DeflateStream(input, CompressionMode.Decompress))
                // Clear the deflate stream after use it
            {
                ds.CopyTo(decompressed);
            }
            return Calculate(decompressed);
        }

        /// <summary>
        /// Returns the CRC32 Hash value with given stream
        /// </summary>
        /// <param name="input">Uncompressed stream to check</param>
        /// <returns>CRC32 hash value</returns>
        private uint Calculate(Stream input)
        {
            input.Position = 0;
            uint result = 0xFFFFFFFFU;
            byte[] buffer = new byte[BufferSize];
            const int readSize = BufferSize;
            int count = input.Read(buffer, 0, readSize);
            while (count > 0)
            {
                for (int i = 0; i < count; i++)
                {
                    result = result >> 8 ^ _crc32Table[(buffer[i]) ^ (result & 0xFFU)];
                }
                count = input.Read(buffer, 0, readSize);
            }
            return ~result;
        }

        protected internal uint CalculateCrc32(Stream input, out Stream compressed)
        {
            input.Position = 0;
            compressed = new MemoryStream();
            using (DeflateStream ds = new DeflateStream(compressed, CompressionMode.Compress, true))
            {
                input.CopyTo(ds);
            }
            return Calculate(input);
        }
    }
}